﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VRacer.Packets;
using System.Threading;
using System.IO;

namespace VRacer.Connection
{
    /// <summary>
    /// Realizuje asynchroniczne wysylanie pakietow. Po polaczeniu startowany jest watek, ktory sprawdza co <c>SLEEPTIME</c> czy kolejka nie zawiera pakietow do wyslania.
    /// Dodanie pakietu do kolejki realizowane jest przy pomocy metody Send(PacketBase packet).
    /// </summary>
    public class Sender : IDisposable
    {
        protected const int SLEEPTIME = 250;
        protected const int PINGTIMEOUT = 180000;

        public event PacketSentHandler PacketSent;

        private Queue<PacketBase> _packetQueue = new Queue<PacketBase>();
        private ClientConnection _connection;
        private Thread _thread;
        private Timer _connectionAliveTimer;
        private BinaryWriter _writer;


        public Sender(ClientConnection connection)
        {
            _connection = connection;

            _thread = new Thread(new ThreadStart(checkAndProcessQueue));   // towrzymy nowy watek
            _thread.IsBackground = true;
            _connection.Connected += _connection_Connected;
            _connectionAliveTimer = new Timer(new TimerCallback(_connectionAlive_Timeout), null, -1, -1);
        }

        void _connectionAlive_Timeout(object param)
        {
            Send(PingPacket.Default);
        }

        void _connection_Connected(object sender, EventArgs e)
        {
            _packetQueue.Clear();
            _writer = new BinaryWriter(_connection.Stream);
            if(!_thread.IsAlive)
                _thread.Start(); 

            _connection.Connected -= _connection_Connected;
            _connection.Disconnected += _connection_Disconnected;

            if (_connection.KeepAlive)
                _connectionAliveTimer.Change(PINGTIMEOUT, PINGTIMEOUT);
        }
        
        void _connection_Disconnected(object sender, EventArgs e)
        {
            //_thread.Abort();
            _writer = null;
            _connectionAliveTimer.Change(0, 0);
            _connection.Connected += _connection_Connected;
            _connection.Disconnected -= _connection_Disconnected;
        }

        /// <summary>
        /// Sprawdza czy w kolejce nie ma pakietu do wyslania
        /// </summary>
        private void checkAndProcessQueue()
        {
            try
            {
                while (true)
                {
                    //jesli NIE jest dostepny header to czekaj dalej
                    if (!_connection.IsActive || _packetQueue.Count == 0)
                    {
                        Thread.Sleep(SLEEPTIME);
                        continue;
                    }

                    PacketBase packet = _packetQueue.Dequeue();
                    packet.Write(_writer);
                    _connection.Stream.Flush();
                    OnPackedSent(packet);
                }
            }
            catch (ThreadAbortException) { }
            catch (Exception e) { _connection.Disconnect(e); }
        }

        public virtual void OnPackedSent(PacketBase packet)
        {
            if (PacketSent != null)
                PacketSent.BeginInvoke(this, packet, null, null);
        }

        private ClientConnection Connection
        {
            get { return _connection; }
            set { _connection = value; }
        }

        /// <summary>
        /// Wysyla pakiet przez strumien polaczenia
        /// </summary>
        /// <param name="packet">The packet.</param>
        public void Send(PacketBase packet)
        {
            _packetQueue.Enqueue(packet);
        }

        #region IDisposable Members

        public void Dispose()
        {
            _thread.Abort();
        }

        #endregion
    }
}
